package com.xnx.bincker.leader.core.aop;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.xnx.bincker.leader.core.annotation.IgnoreProperties;
import com.xnx.bincker.leader.core.annotation.OnlyIncludeProperties;
import com.xnx.bincker.leader.utils.Pair;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.ArrayMemberValue;
import javassist.bytecode.annotation.StringMemberValue;
import org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.*;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 返回到前台POJO对象的字段过滤混淆生成器
 */
@Aspect
@Component
public class ResponsePojoIgnorePropertyMixinGenerator {
    private static final Map<Method, Map<Class<?>, Class<?>>> CACHE = new ConcurrentHashMap<>();
    private static final ThreadLocal<Map<Class<?>, Class<?>>> CURRENT_MIXIN = new ThreadLocal<>();

    @Before("execution(* com.xnx.bincker.leader.controller..*.*(..))")
    public void doBefore(JoinPoint point){
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Method method = methodSignature.getMethod();
        if(!isRequestMethod(method)) return;
        Map<Class<?>, Class<?>> mixinMap = CACHE.computeIfAbsent(method, this::getMixinMap);
        CURRENT_MIXIN.set(mixinMap);
    }

    /**
     * 判断是否为请求方法
     */
    private static boolean isRequestMethod(Method method){
        return method.isAnnotationPresent(RequestMapping.class)
                || method.isAnnotationPresent(GetMapping.class)
                || method.isAnnotationPresent(PostMapping.class)
                || method.isAnnotationPresent(PutMapping.class)
                || method.isAnnotationPresent(DeleteMapping.class);
    }

    /**
     * 获取方法的JSON过滤Mixin
     * @param method 方法
     * @return mixin列表
     */
    private Map<Class<?>, Class<?>> getMixinMap(Method method){
        Map<Class<?>, Set<String>> ignoreInfo = getIgnoreInfo(method);
        return ignoreInfo.entrySet().stream().map(entity-> {
            try {
                return new Pair<Class<?>, Class<?>>(entity.getKey(), generateJsonMixinInterface(entity.getKey(), entity.getValue()));
            } catch (CannotCompileException e) {
                throw new RuntimeException("创建JSON过滤mixin失败", e);
            }
        }).collect(HashMap::new, (map, item)-> map.put(item.getFirst(), item.getSecond()), HashMap::putAll);
    }

    /**
     * 通过方法获取过滤信息
     * @param method 方法
     */
    public static Map<Class<?>, Set<String>> getIgnoreInfo(Method method){
        Class<?> cls = method.getDeclaringClass();
        IgnoreProperties[] clsIgnoreAnnotation = cls.getAnnotationsByType(IgnoreProperties.class);
        IgnoreProperties[] methodIgnoreAnnotation = method.getAnnotationsByType(IgnoreProperties.class);
        OnlyIncludeProperties[] clsIncludeAnnotation = cls.getAnnotationsByType(OnlyIncludeProperties.class);
        OnlyIncludeProperties[] methodIncludeAnnotation = method.getAnnotationsByType(OnlyIncludeProperties.class);
        Map<Class<?>, Set<String>> result = new HashMap<>();
//        类注解的不包含属性
        for (IgnoreProperties annotation : clsIgnoreAnnotation) {
            result.computeIfAbsent(annotation.target(), k -> new HashSet<>())
                    .addAll(Arrays.asList(annotation.value()));
        }
//        方法注解的不包含属性
        for (IgnoreProperties annotation : methodIgnoreAnnotation) {
            result.computeIfAbsent(annotation.target(), k -> new HashSet<>())
                    .addAll(Arrays.asList(annotation.value()));
        }
//        包含属性
        setIncludeInfo(clsIncludeAnnotation, result);
        setIncludeInfo(methodIncludeAnnotation, result);
        return result;
    }

    /**
     * 设置包含属性信息
     * @param includeAnnotations 包含属性注解
     * @param result 数据集
     */
    private static void setIncludeInfo(OnlyIncludeProperties[] includeAnnotations, Map<Class<?>, Set<String>> result) {
        for (OnlyIncludeProperties annotation : includeAnnotations) {
             Class<?> target = annotation.target();
            Set<String> value = Stream.of(BeanUtils.getPropertyDescriptors(target))
                    .map(PropertyDescriptor::getName)
                    .filter(fieldName-> !ArrayUtils.contains(annotation.value(), fieldName))
                    .collect(Collectors.toSet());
            result.put(annotation.target(), value);
        }
    }

    /**
     * 创建JSON过滤用的Mixin
     * @param cls 过滤的POJO类
     * @param ignoreProperties 过滤的属性名
     * @return 注解了JsonIgnoreProperties的接口
     * @throws CannotCompileException 当CTClass转换为Class时可能会抛出的异常
     */
    public static Class<?> generateJsonMixinInterface(Class<?> cls, Set<String> ignoreProperties) throws CannotCompileException {
        ClassPool classPool = ClassPool.getDefault();
        String className = "com.xnx.bincker.leader.mixin.JsonMixin$" + Integer.toString(Objects.hash(cls, ignoreProperties), 36);
        CtClass mixin;
        try {
//            如果已经创建过，则直接返回
            return Objects.requireNonNull(ClassUtils.getDefaultClassLoader()).loadClass(className);
        }  catch (ClassNotFoundException ignore) {}
//        创建接口
        mixin = classPool
                .makeInterface(className);

        ClassFile mixinInterfaceFile = mixin.getClassFile();
        ConstPool constPool = mixinInterfaceFile.getConstPool();

//        创建注解
        AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation annotation = new Annotation(JsonIgnoreProperties.class.getName(), constPool);
        ArrayMemberValue ignorePropertyValues = new ArrayMemberValue(constPool);
        ignorePropertyValues
                .setValue(
                        ignoreProperties.stream()
                                .map(propertyName-> new StringMemberValue(propertyName, constPool))
                                .collect(Collectors.toList())
                                .toArray(new StringMemberValue[]{})
                );

//        设置注解到接口
        annotation.addMemberValue("value", ignorePropertyValues);
        annotationsAttribute.addAnnotation(annotation);
        mixinInterfaceFile.addAttribute(annotationsAttribute);
        return mixin.toClass();
    }

    /**
     * 获取当前线程的Mixin
     */
    public static Optional<Map<Class<?>, Class<?>>> getCurrentMixin(){
        return Optional.ofNullable(CURRENT_MIXIN.get());
    }
}
